package scuts.core;
import scuts.core.types.Future;
import scuts.core.types.ProgressiveFuture;
import scuts.core.types.LazyIterator;
import scuts.core.types.Option;
import scuts.core.types.Either;

using scuts.core.lifting.ProgressiveFutureLifting;
using scuts.core.lifting.OptionLifting;
using scuts.core.lifting.EitherLifting;
using scuts.core.lifting.FutureLifting;


class MapProgressiveFutureEither {
  public static function map < A,B,C,D > (w:ProgressiveFuture<Either<A,B>>, left:A->C, right:B->D):ProgressiveFuture<Either<C,D>>
  {
    var f = function ( e:Either<A,B>) return MapEither.map(e, left, right);
    
    return MapProgressiveFuture.map(w, f);
  }
}

class MapArrayProgressiveFuture {
  public static function map < S, T > (w:Array<ProgressiveFuture<S>>, f:S->T):Array<ProgressiveFuture<T>>
  {
    var r = [];
    for (fut in w) {
      r.push(MapProgressiveFuture.map(fut, f));
    }
    return r;
  }
}

class MapFutureEither {
  public static function map < A,B,C,D > (w:Future<Either<A,B>>, left:A->C, right:B->D):Future<Either<C,D>>
  {
    var f = function ( e:Either<A,B>) return MapEither.map(e, left, right);
    
    return MapFuture.map(w, f);
  }
}

class MapArrayFuture {
  public static function map < S, T > (w:Array<Future<S>>, f:S->T):Array<Future<T>>
  {
    var r = [];
    for (fut in w) {
      r.push(MapFuture.map(fut, f));
    }
    return r;
  }
}

class MapArrayOption {
  public static function map < S, T > (arr:Array<Option<S>>, f:S->T):Array<Option<T>> {
    var r = [];
    for (o in arr) {
      r.push(MapOption.map(o, f));
    }
    return r;
  }
}


class MapFuture {
  
  public static inline function map < S, T > (w:Future<S>, f:S->T):Future<T>
  {
    return f.liftWithFuture()(w);
  }
  
  
  public static inline function map2 < S1, S2, T> (a:Future<S1>, f:S1->S2->T, b:Future<S2>):Future<T>
  {
    return f.liftWithFuture()(a, b);
  }
  
  public static inline function map3 < S1, S2, S3, T> (a:Future<S1>, f:S1->S2->S3->T, b:Future<S2>, c:Future<S3>):Future<T>
  {
    return f.liftWithFuture()(a, b, c);
  }
  
  public static inline function map4 < S1, S2, S3, S4, T> (a:Future<S1>, f:S1->S2->S3->S4->T, b:Future<S2>, c:Future<S3>, d:Future<S4>):Future<T>
  {
    return f.liftWithFuture()(a, b, c, d);
  }
}

class MapProgressiveFuture {
  public static inline function map < S, T > (w:ProgressiveFuture<S>, f:S->T):ProgressiveFuture<T>
  {
    return f.liftWithProgressiveFuture()(w);
  }
  
  public static inline function map2 < S1, S2, T> (a:ProgressiveFuture<S1>, f:S1->S2->T, b:ProgressiveFuture<S2>):ProgressiveFuture<T>
  {
    return f.liftWithProgressiveFuture()(a, b);
  }
  
  public static inline function map3 < S1, S2, S3, T> (a:ProgressiveFuture<S1>, f:S1->S2->S3->T, b:ProgressiveFuture<S2>, c:ProgressiveFuture<S3>):ProgressiveFuture<T>
  {
    return f.liftWithProgressiveFuture()(a, b, c);
  }
  
  public static inline function map4 < S1, S2, S3, S4, T> (a:ProgressiveFuture<S1>, f:S1->S2->S3->S4->T, b:ProgressiveFuture<S2>, c:ProgressiveFuture<S3>, d:ProgressiveFuture<S4>):ProgressiveFuture<T>
  {
    return f.liftWithProgressiveFuture()(a, b, c, d);
  }
}

class MapEither {
  public static function mapLeft < A,B,C > (e:Either<A,B>, f:A->C):Either<C,B>
  {
    return f.liftWithEitherLeft()(e);
  }
  
  public static function mapRight < A,B,C > (e:Either<A,B>, f:B->C):Either<A,C>
  {
    return f.liftWithEitherRight()(e);
  }
  
  public static function map < A,B,C,D > (o:Either<A,B>, left:A->C, right:B->D):Either<C,D>
  {
    return switch (o) {
      case Left(v): Left(left(v));
      case Right(v): Right(right(v));
    }
  }
  
}

class MapOption {
  public static inline function map < S, T > (o:Option<S>, f:S->T):Option<T> {
    return f.liftWithOption()(o);
  }
  public static inline function map2 < S1, S2, T > (o1:Option<S1>, f:S1->S2->T, o2:Option<S2>):Option<T> {
    return f.liftWithOption()(o1, o2);
  }
}





class MapArray 
{
	public static function map < A, B > (arr:Array<A>, f:A->B):Array<B> 
	{
		return MapIterable.mapToArray(arr, f);
	}
	
	public static function mapWithIndex < A, B > (arr:Array<A>, f:A->Int->B):Array<B> 
	{
		var r = [];
		for (i in 0...arr.length) {
			r.push(f(arr[i], i));
		}
		return r;
	}
}

class MapList {
  public static function map < A, B > (list:List<A>, f:A->B):List<B> 
	{
		return MapIterable.mapToList(list, f);
	}
	
	public static function mapWithIndex < A, B > (list:List<A>, f:A->Int->B):List<B> 
	{
		return MapIterable.mapToListWithIndex(list, f);
	}
}

class MapIterable 
{
  public static function mapToList < A, B > (iterable:Iterable<A>, f:A->B):List<B> 
	{
		var r = new List();
		for (i in iterable) {
			r.add(f(i));
		}
		return r;
	}
  
  public static function mapToIterator < A, B > (iterable:Iterable<A>, f:A->B):Iterator<B> 
	{
		return MapIterator.map(iterable.iterator(), f);
	}
  
  public static function mapToIteratorWithIndex < A, B > (iterable:Iterable<A>, f:A->Int->B):Iterator<B> 
	{
		return MapIterator.mapWithIndex(iterable.iterator(), f);
	}
  
	public static function mapToArray < A, B > (iterable:Iterable<A>, f:A->B):Array<B> 
	{
		var r = [];
		for (i in iterable) {
			r.push(f(i));
		}
		return r;
	}
	
	public static function mapToListWithIndex < A, B > (iterable:Iterable<A>, f:A->Int->B):List<B> 
	{
		var r = new List();
    var index = 0;
		for (e in iterable) {
			r.add(f(e, index));
      index++;
		}
		return r;
	}
	
	
	public static function mapToArrayWithIndex < A, B > (iterable:Iterable<A>, f:A->Int->B):Iterable<B> 
	{
		var r = [];
    var index = 0;
		for (i in iterable) {
			r.push(f(i, index++));
		}
		return r;
	}
}

class MapIterator 
{
  public static function map < A, B > (it:Iterator<A>, f:A->B):Iterator<B> 
	{
    return {
      hasNext : function () {
        return it.hasNext();
      },
      next : function () {
        return f(it.next());
      }
    }
	}
  
  public static function mapWithIndex < A, B > (it:Iterator<A>, f:A->Int->B):Iterator<B> 
	{
    var index = 0;
    return {
      hasNext : function () {
        return it.hasNext();
      },
      next : function () {
        return f(it.next(), index++);
      }
    }
	}
  
	public static function mapToList < A, B > (it:Iterator<A>, f:A->B):List<B> 
	{
		var r = new List();
		for (e in it) 
		{
			r.push(f(e));
		}
		return r;
	}
	
	public static function mapToListWithIndex < A, B > (it:Iterator<A>, f:A->Int->B):List<B> 
	{
		var r = new List();
		var i = 0;
		for (e in it) 
		{
			r.push(f(e, i++));
		}
		return r;
	}
	
	public static function mapToArray < A, B > (it:Iterator<A>, f:A->B):Array<B> 
	{
		var r = [];
		for (e in it) 
		{
			r.push(f(e));
		}
		return r;
	}
	
	public static function mapToArrayWithIndex < A, B > (it:Iterator<A>, f:A->Int->B):Array<B> 
	{
		var r = [];
		var i = 0;
		for (e in it) 
		{
			r.push(f(e, i++));
		}
		return r;
	}
}

class MapLazyIterator 
{
  public static function map < A, B > (it:LazyIterator<A>, f:A->B):LazyIterator<B> 
	{
    return function () return MapIterator.map(it(), f);
	}
  
  public static function mapWithIndex < A, B > (it:LazyIterator<A>, f:A->Int->B):LazyIterator<B> 
	{
    return function () return MapIterator.mapWithIndex(it(), f); 
	}
}